#include "DiskServices.h"

	DiskServices::DiskServices(int diskSizeInBytes, int dataBlockSize, int numberOfInodes) :
		physicalDisk (diskSizeInBytes, dataBlockSize, numberOfInodes) {
		allocatedInodes.clear();
	}

	DiskServices::DiskServices ():physicalDisk () {
		allocatedInodes.clear();
		int currInodeOffset = 3*physicalDisk.getBlockSize();
		cout << "Loading disk data from FILE_SYS" << endl;
		for (int i=0; i<physicalDisk.getNumOfInodes(); i++) {
			createInodeFromDisk(currInodeOffset);
			currInodeOffset+=50;
		}
		/*
		for (int i=0; i<physicalDisk.getNumOfInodes(); i++) {
			cout << allocatedInodes[i]->getType() << endl;
		}
		*/
	}


	DiskServices::~DiskServices() {}

	int DiskServices::getBlockSize() {
		return physicalDisk.getBlockSize();
	}

	int DiskServices::allocateInode() {
		int inodeOffset = physicalDisk.allocateInode();
		int indirectDataBlockNum = allocateDataBlock();
		if (inodeOffset == -1 || indirectDataBlockNum == -1) {
			cerr << "Not enough space in disk for a new inode" << endl;
			return -1;
		} else {
			int inodeNum = (inodeOffset - 3*physicalDisk.getBlockSize())/50;
			//int directDataBlocks [10];
			Inode *newInode = new Inode (inodeNum, inodeOffset,
					indirectDataBlockNum*physicalDisk.getBlockSize(),
					physicalDisk.getBlockSize());
			allocatedInodes.insert(pair<int,Inode*> (inodeNum,newInode));
			newInode->writeToPhysicalDisk();
			return inodeNum;
		}
	}

	void DiskServices::incHardLinksNum (int inode) {
		map<int,Inode*>::iterator iter = allocatedInodes.find(inode);
		if (iter == allocatedInodes.end()) {
			cerr << "Can`t increase hardlinks of inode " << inode  << ". He is not allocated" << endl;
			return;
		} else {
			iter->second->incNumOfHardLinks();
			iter->second->writeToPhysicalDisk();
		}
	}

	void DiskServices::decHardLinksNum (int inode) {
		map<int,Inode*>::iterator iter = allocatedInodes.find(inode);
		if (iter == allocatedInodes.end()) {
			cerr << "Can`t decrease hardlinks of inode " << inode  << ". He is not allocated" << endl;
			return;
		} else {
			iter->second->decNumOfHardLinks();
			iter->second->writeToPhysicalDisk();
		}
	}

	void DiskServices::freeInode(int inode) {
		map<int,Inode*>::iterator iter = allocatedInodes.find(inode);
		if (iter == allocatedInodes.end()) {
			cerr << "Can`t free inode " << inode  << ". He is not allocated" << endl;
			return;
		} else {
			if (iter->second->getNumOfHardLinks() > 0) {
				cerr << "Can`t free inode " << inode  << ". He has hard links to him" << endl;
				return;
			}
			//free all the blocks held by this inode:
			int inodeNum = iter->second->getNumber();
			int i=0;
			int currDataBlock = getDataBlock(inodeNum,i);
			while (currDataBlock != -1) {
				freeDataBlock(currDataBlock);
				i++;
				currDataBlock = getDataBlock(inodeNum,i);
			}
			//free the indirect block:
			freeDataBlock(iter->second->getIndirectBlockNum());
			//free the inode and updates FILE_SYS
			int inodeOffset = iter->second->getOffset();
			physicalDisk.freeInode(inodeOffset);
			//delete(iter->second);
			allocatedInodes.erase(iter);
		}
	}

	void DiskServices::resetInode(int inode) {
		map<int,Inode*>::iterator iter = allocatedInodes.find(inode);
		if (iter == allocatedInodes.end()) {
			cerr << "Can`t reset inode " << inode  << ". He is not allocated" << endl;
			return;
		} else {
			//free all the blocks held by this inode:
			int inodeNum = iter->second->getNumber();
			int i=0;
			while (iter->second->hasAllocatedBlocks()) {
				int currDataBlock = getDataBlock(inodeNum,i);
				freeDataBlock(currDataBlock);
				iter->second->decreaseBlocksCounter();
				i++;
			}
		}
	}

	int DiskServices::allocateDataBlock() {
		int blockOffset = physicalDisk.allocateBlock();
		if (blockOffset == -1) {
			cerr << "Not enough space in disk for a new data block" << endl;
			return -1;
		} else {
			int blockNum = blockOffset/physicalDisk.getBlockSize();
			/*
			DataBlock *newDataBlock = new DataBlock (blockNum, blockOffset,
					physicalDisk.getBlockSize());
			allocatedBlocks.insert(pair<int,DataBlock*> (blockNum, newDataBlock));
			*/
			return blockNum;
		}
	}

	void DiskServices::freeDataBlock (int dblock) {
		/*
		map<int,DataBlock*>::iterator iter = allocatedBlocks.find(dblock);
		if (iter == allocatedBlocks.end()) {
			cerr << "Block " << dblock  << " is not allocated" << endl;
			return;
		} else {
			int blockOffset = iter->second->getOffset();
			//frees the block and updates FILE_SYS
			physicalDisk.freeBlock(blockOffset);
			//delete(iter->second);
			allocatedBlocks.erase(iter);
		}
		*/
		physicalDisk.freeBlock(dblock*physicalDisk.getBlockSize());

	}

	char DiskServices::getInodeType (int inode) {
		map<int,Inode*>::iterator iter = allocatedInodes.find(inode);
		if (iter == allocatedInodes.end()) {
			cerr << "Inode " << inode  << " is not allocated" << endl;
			return ' ';
		} else {
			return iter->second->getType();
		}
	}


	void DiskServices::setInodeType (int inode, char filetype) {
		map<int,Inode*>::iterator iter = allocatedInodes.find(inode);
		if (iter == allocatedInodes.end()) {
			cerr << "Inode " << inode  << " is not allocated" << endl;
			return;
		} else {
			iter->second->setType(filetype);
			iter->second->writeToPhysicalDisk();
		}
	}

	int DiskServices::getDataBlock (int inode, int i) {
		map<int,Inode*>::iterator iter = allocatedInodes.find(inode);
		if (iter == allocatedInodes.end()) {
			//cerr << "Inode " << inode  << " is not allocated" << endl;
			return -1;
		} else {
			int blockNum = iter->second->getDataBlock(i);
			if (blockNum == -1) {
				//cerr << "Block " << i << " is not allocated in inode " << inode << endl;
			}
			return blockNum;
		}
	}

	bool DiskServices::setDataBlock (int inode, int i, int dblockNum) {
		map<int,Inode*>::iterator iter = allocatedInodes.find(inode);
		if (iter == allocatedInodes.end()) {
			cerr << "Inode " << inode  << " is not allocated" << endl;
			return false;
		} else {
			int result = iter->second->setDataBlock(i, dblockNum);
			if (result == -1) {
				return false;
			} else {
				iter->second->writeToPhysicalDisk();
				return true;
			}
		}
	}

	int DiskServices::readBlock (int dblockNum, unsigned char buf[]) {
		return DiskUtils::readFromDisk(buf, physicalDisk.getBlockSize()*dblockNum,
				physicalDisk.getBlockSize()*sizeof(char));
	}

	int DiskServices::writeBlock (int dblockNum, unsigned char *newData) {
		return DiskUtils::writeToDisk(newData, physicalDisk.getBlockSize()*dblockNum,
				physicalDisk.getBlockSize()*sizeof(char));
	}

	int DiskServices::getFileSize (int inode) {
		map<int,Inode*>::iterator iter = allocatedInodes.find(inode);
		if (iter == allocatedInodes.end()) {
			cerr << "Inode " << inode  << " is not allocated" << endl;
			return -1;
		} else {
			return iter->second->getFileSize();
		}
	}


	void DiskServices::setFileSize (int inode, int newSize) {
		map<int,Inode*>::iterator iter = allocatedInodes.find(inode);
		if (iter == allocatedInodes.end()) {
			cerr << "Inode " << inode  << " is not allocated" << endl;
			return;
		} else {
			int blockSize = physicalDisk.getBlockSize();
			//max capacity using 10 direct and 1 indirect
			if (newSize <= (10 * blockSize + ((blockSize/4) * blockSize))) {
				iter->second->setFileSize(newSize);
				iter->second->writeToPhysicalDisk();
			} else {
				cerr << "File size " << newSize  << " is too large" << endl;
				return;
			}
		}
	}

	void DiskServices::createInodeFromDisk(int inodeOffset) {
		char inodeType;
		int fileSize;
		int numOfHardLinks;
		vector<int> inodeBlocks;
		int inodesIndirectBlockOffset;
		bool hasMoreBlocks = true;
		inodeBlocks.clear();

		DiskUtils::readFromDisk(&inodeType,inodeOffset,sizeof(char));
		if (inodeType != 0) {
			int inodeNum = (inodeOffset - 3*physicalDisk.getBlockSize())/50;
			DiskUtils::readFromDisk(&numOfHardLinks,inodeOffset+1,sizeof(char));
			DiskUtils::readFromDisk(&fileSize,inodeOffset+2,sizeof(int));
			DiskUtils::readFromDisk(&inodesIndirectBlockOffset,inodeOffset+46,sizeof(int));
			//reading the numbers of the direct blocks:
			int i=0;
			int blockNum;
			while (hasMoreBlocks && inodeBlocks.size() < 10) {
				DiskUtils::readFromDisk(&blockNum,inodeOffset+6+i*4,sizeof(int));
				if (blockNum != 0) {
					inodeBlocks.push_back(blockNum);
				} else {
					hasMoreBlocks = false;
				}
				i++;
			}
			//reading more inode`s blocks from the indirect block:
			i=0;
			while (hasMoreBlocks && (int)inodeBlocks.size() < 10+physicalDisk.getBlockSize()/4) {
				DiskUtils::readFromDisk(&blockNum,inodesIndirectBlockOffset+i*4,sizeof(int));
				if (blockNum != 0) {
					inodeBlocks.push_back(blockNum);
				} else {
					hasMoreBlocks = false;
				}
				i++;
			}

			Inode *newInode = new Inode (inodeNum, inodeOffset, inodeType, numOfHardLinks,
										fileSize, inodeBlocks, inodesIndirectBlockOffset,
										physicalDisk.getBlockSize());

			newInode->setType(inodeType);
						allocatedInodes.insert(pair<int,Inode*> (inodeNum,newInode));
		}
	}
